import os
import re
import tkinter as tk
from tkinter import filedialog, ttk, messagebox, scrolledtext, Listbox, MULTIPLE
from pathlib import Path
import chardet

class CodeToTXTConverter:
    def __init__(self):
        self.project_path = ""
        self.output_file = ""
        # 忽略的目录（可自定义）
        self.ignore_dirs = ["build", ".git", "vendor", "node_modules", "bin", "obj", "cmake-build-debug"]
        # 忽略的文件（可自定义）
        self.ignore_files = [".DS_Store", "thumbs.db"]
        # 默认支持的代码文件扩展名（可由用户在GUI中修改）
        self.code_extensions = ['.h', '.cpp', '.hpp', '.c', '.cc']
        self.file_groups = []
        self.main_cpp = None

    def set_project_path(self, path):
        self.project_path = path
        self._analyze_project_structure()

    def set_output_file(self, file_path):
        self.output_file = file_path

    def _analyze_project_structure(self):
        """分析项目结构，创建文件组"""
        if not self.project_path:
            return
            
        code_files = self._find_code_files(self.project_path)
        
        # 分离main.cpp
        self.main_cpp = None
        filtered_code_files = []
        for file_path in code_files:
            if os.path.basename(file_path) == "main.cpp":
                self.main_cpp = file_path
            else:
                filtered_code_files.append(file_path)

        # 按文件名分组
        h_files = {}
        cpp_files = {}
        for file_path in filtered_code_files:
            base_name = os.path.splitext(os.path.basename(file_path))[0]
            ext = os.path.splitext(file_path)[1].lower()
            if ext == '.h':
                h_files[base_name] = file_path
            elif ext == '.cpp':
                cpp_files[base_name] = file_path

        # 配对处理
        self.file_groups = []
        paired_bases = sorted(set(h_files.keys()) & set(cpp_files.keys()))
        for base in paired_bases:
            self.file_groups.append((base, (h_files[base], cpp_files[base])))

        # 未配对文件
        for base in sorted(set(h_files.keys()) - set(paired_bases)):
            self.file_groups.append((f"{base}.h", (h_files[base],)))
        
        for base in sorted(set(cpp_files.keys()) - set(paired_bases)):
            self.file_groups.append((f"{base}.cpp", (cpp_files[base],)))
        
        # 添加main.cpp
        if self.main_cpp:
            self.file_groups.append(("main.cpp", (self.main_cpp,)))

    def get_group_names(self):
        """获取所有类/文件组的名称"""
        return [name for name, _ in self.file_groups]

    def generate_txt(self, selected_groups):
        if not self.project_path:
            raise ValueError("项目路径未设置")
        if not self.output_file:
            self.output_file = os.path.join(self.project_path, "code_documentation.txt")

        txt_content = []
        
        # 处理选中的组
        for group_name, files in self.file_groups:
            if group_name in selected_groups:
                for file_path in files:
                    self._add_file_content(file_path, txt_content)
                
                # 如果是配对文件，在.h和.cpp之间使用单换行
                if len(files) == 2:
                    txt_content.append("\n")
                # 不同类之间使用双换行
                txt_content.append("\n")

        # 写入文件
        with open(self.output_file, 'w', encoding='utf-8') as f:
            f.write("".join(txt_content))
        
        return self.output_file

    def _add_file_content(self, file_path, txt_content):
        """添加单个文件内容到输出，只包含纯代码和注释"""
        try:
            with open(file_path, 'rb') as f:
                raw_data = f.read(4096)
                result = chardet.detect(raw_data)
                encoding = result['encoding'] or 'utf-8'
            
            with open(file_path, 'r', encoding=encoding, errors='replace') as f:
                code = f.read()
            
            # 确保文件内容以换行符结束
            if code and code[-1] != '\n':
                code += '\n'
            txt_content.append(code)
            
        except Exception as e:
            txt_content.append(f"无法读取文件内容: {str(e)}\n")

    def _find_code_files(self, path):
        code_files = []
        for root, dirs, files in os.walk(path):
            dirs[:] = [d for d in dirs if d not in self.ignore_dirs]
            for file in files:
                if file.lower() in self.ignore_files:
                    continue
                file_path = os.path.join(root, file)
                ext = os.path.splitext(file)[1].lower()
                if ext in self.code_extensions:
                    if self._is_text_file(file_path):
                        code_files.append(file_path)
        return code_files

    def _is_text_file(self, file_path):
        try:
            with open(file_path, 'rb') as f:
                chunk = f.read(1024)
                if b'\0' in chunk:
                    return False
            return True
        except Exception as e:
            print(f"无法读取文件 {file_path}: {e}")
            return False

class CodeToTXTApp:
    def __init__(self, root):
        self.root = root
        self.root.title("C/C++ 代码转 TXT 文档工具")
        self.root.geometry("900x700")
        self.root.minsize(800, 600)
        
        self.converter = CodeToTXTConverter()
        
        # 设置样式
        self.style = ttk.Style()
        self.style.configure("TFrame", background="#f0f0f0")
        self.style.configure("TLabel", background="#f0f0f0", font=("Arial", 10))
        self.style.configure("Header.TLabel", background="#3a7ebf", foreground="white", font=("Arial", 12, "bold"))
        self.style.configure("TButton", font=("Arial", 10))
        self.style.configure("TNotebook", background="#f0f0f0")
        self.style.configure("TNotebook.Tab", font=("Arial", 10, "bold"))
        
        # 主界面布局
        self.main_frame = ttk.Frame(root)
        self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 顶部标题
        header_frame = ttk.Frame(self.main_frame)
        header_frame.pack(fill=tk.X, pady=(0, 10))
        self.header_label = ttk.Label(
            header_frame, 
            text="C/C++ 项目代码转 TXT 文档工具",
            style="Header.TLabel"
        )
        self.header_label.pack(fill=tk.X, ipady=10)
        
        # 配置区域
        config_frame = ttk.LabelFrame(self.main_frame, text="配置选项")
        config_frame.pack(fill=tk.X, pady=(0, 10))
        
        # 项目路径选择
        project_frame = ttk.Frame(config_frame)
        project_frame.pack(fill=tk.X, pady=5)
        ttk.Label(project_frame, text="项目路径:").pack(side=tk.LEFT, padx=(0, 5))
        self.project_path_var = tk.StringVar()
        self.project_entry = ttk.Entry(project_frame, textvariable=self.project_path_var, width=50)
        self.project_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        ttk.Button(project_frame, text="浏览...", command=self.browse_project).pack(side=tk.LEFT)
        
        # 输出文件选择
        output_frame = ttk.Frame(config_frame)
        output_frame.pack(fill=tk.X, pady=5)
        ttk.Label(output_frame, text="输出文件:").pack(side=tk.LEFT, padx=(0, 5))
        self.output_path_var = tk.StringVar()
        self.output_entry = ttk.Entry(output_frame, textvariable=self.output_path_var, width=50)
        self.output_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        ttk.Button(output_frame, text="浏览...", command=self.browse_output).pack(side=tk.LEFT)
        
        # 文件扩展名配置
        extensions_frame = ttk.Frame(config_frame)
        extensions_frame.pack(fill=tk.X, pady=5)
        ttk.Label(extensions_frame, text="文件扩展名:").pack(side=tk.LEFT, padx=(0, 5))
        self.extensions_var = tk.StringVar(value=".h;.cpp;.hpp;.c;.cc")
        self.extensions_entry = ttk.Entry(extensions_frame, textvariable=self.extensions_var, width=50)
        self.extensions_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        ttk.Button(
            extensions_frame, 
            text="默认", 
            command=lambda: self.extensions_var.set(".h;.cpp;.hpp;.c;.cc")
        ).pack(side=tk.LEFT)
        
        # 类选择区域
        class_frame = ttk.LabelFrame(self.main_frame, text="选择要导出的类/文件")
        class_frame.pack(fill=tk.BOTH, expand=True, pady=(5, 10))
        
        # 类选择列表框
        self.class_listbox = Listbox(
            class_frame, 
            selectmode=MULTIPLE,
            font=("Arial", 10),
            bg="white"
        )
        self.class_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 按钮区域
        button_frame = ttk.Frame(self.main_frame)
        button_frame.pack(fill=tk.X, pady=(5, 10))
        
        self.convert_button = ttk.Button(
            button_frame, 
            text="转换选中的类", 
            command=self.convert_selected_classes,
            style="TButton"
        )
        self.convert_button.pack(side=tk.LEFT, padx=5)
        
        self.select_all_button = ttk.Button(
            button_frame, 
            text="全选", 
            command=self.select_all_classes
        )
        self.select_all_button.pack(side=tk.LEFT, padx=5)
        
        self.deselect_all_button = ttk.Button(
            button_frame, 
            text="全不选", 
            command=self.deselect_all_classes
        )
        self.deselect_all_button.pack(side=tk.LEFT, padx=5)
        
        self.preview_button = ttk.Button(
            button_frame, 
            text="预览文档", 
            command=self.preview_document,
            state=tk.DISABLED
        )
        self.preview_button.pack(side=tk.LEFT, padx=5)
        
        self.open_button = ttk.Button(
            button_frame, 
            text="打开输出文件", 
            command=self.open_output,
            state=tk.DISABLED
        )
        self.open_button.pack(side=tk.LEFT, padx=5)
        
        # 状态栏
        self.status_var = tk.StringVar()
        self.status_var.set("就绪")
        status_bar = ttk.Label(self.main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 日志区域
        log_frame = ttk.LabelFrame(self.main_frame, text="处理日志")
        log_frame.pack(fill=tk.BOTH, expand=True, pady=(5, 0))
        self.log_text = scrolledtext.ScrolledText(
            log_frame, 
            wrap=tk.WORD, 
            height=12,
            font=("Consolas", 9)
        )
        self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        self.log_text.config(state=tk.DISABLED)
        
        # 初始化默认输出路径
        self.output_path_var.set(os.path.join(os.path.expanduser("~"), "code_documentation.txt"))

    def browse_project(self):
        path = filedialog.askdirectory(
            title="选择C/C++项目目录",
            initialdir=os.path.expanduser("~")
        )
        if path:
            self.project_path_var.set(path)
            project_name = os.path.basename(path)
            output_file = os.path.join(os.path.dirname(path), f"{project_name}_docs.txt")
            self.output_path_var.set(output_file)
            
            # 分析项目结构并更新类列表
            self.converter.set_project_path(path)
            self.update_class_list()

    def browse_output(self):
        path = filedialog.asksaveasfilename(
            title="保存TXT文档",
            initialdir=os.path.expanduser("~"),
            defaultextension=".txt",
            filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
        )
        if path:
            self.output_path_var.set(path)

    def update_options(self):
        extensions = self.extensions_var.get().split(';')
        self.converter.code_extensions = [ext.strip().lower() for ext in extensions if ext.strip()]
        self.converter.set_project_path(self.project_path_var.get())
        self.update_class_list()

    def update_class_list(self):
        """更新类列表显示"""
        self.class_listbox.delete(0, tk.END)
        for group_name in self.converter.get_group_names():
            self.class_listbox.insert(tk.END, group_name)
        # 默认选中所有类
        self.select_all_classes()

    def select_all_classes(self):
        """选择所有类"""
        self.class_listbox.selection_clear(0, tk.END)
        for i in range(self.class_listbox.size()):
            self.class_listbox.selection_set(i)

    def deselect_all_classes(self):
        """取消选择所有类"""
        self.class_listbox.selection_clear(0, tk.END)

    def get_selected_classes(self):
        """获取选中的类"""
        selected_indices = self.class_listbox.curselection()
        return [self.class_listbox.get(i) for i in selected_indices]

    def log_message(self, message):
        self.log_text.config(state=tk.NORMAL)
        self.log_text.insert(tk.END, message + "\n")
        self.log_text.see(tk.END)
        self.log_text.config(state=tk.DISABLED)
        self.status_var.set(message)
        self.root.update_idletasks()

    def convert_selected_classes(self):
        project_path = self.project_path_var.get()
        output_path = self.output_path_var.get()
        
        if not project_path:
            messagebox.showerror("错误", "请选择项目目录")
            return
        
        if not os.path.isdir(project_path):
            messagebox.showerror("错误", "指定的项目目录不存在")
            return
        
        selected_classes = self.get_selected_classes()
        if not selected_classes:
            messagebox.showerror("错误", "请至少选择一个类/文件")
            return
        
        self.log_text.config(state=tk.NORMAL)
        self.log_text.delete(1.0, tk.END)
        self.log_text.config(state=tk.DISABLED)
        self.log_message("开始处理项目...")
        self.log_message(f"项目目录: {project_path}")
        self.log_message(f"输出文件: {output_path}")
        self.log_message(f"选中的类/文件: {', '.join(selected_classes)}")
        
        try:
            self.converter.set_project_path(project_path)
            self.converter.set_output_file(output_path)
            self.update_options()
            
            output_file = self.converter.generate_txt(selected_classes)
            
            self.log_message(f"成功生成TXT文档: {output_file}")
            self.log_message("处理完成!")
            self.preview_button.config(state=tk.NORMAL)
            self.open_button.config(state=tk.NORMAL)
            messagebox.showinfo("成功", f"已成功生成TXT文档:\n{output_file}")
        except Exception as e:
            self.log_message(f"处理过程中出错: {str(e)}")
            messagebox.showerror("错误", f"处理过程中出错:\n{str(e)}")

    def preview_document(self):
        output_path = self.output_path_var.get()
        if not os.path.exists(output_path):
            messagebox.showerror("错误", "输出文件不存在")
            return
        
        try:
            with open(output_path, 'r', encoding='utf-8') as f:
                content = f.read()
            
            temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".txt")
            temp_file.write(content.encode('utf-8'))
            temp_file.close()
            
            os.startfile(temp_file.name)  # Windows系统专用
        except Exception as e:
            messagebox.showerror("预览错误", f"无法预览文档: {str(e)}")

    def open_output(self):
        output_path = self.output_path_var.get()
        if os.path.exists(output_path):
            os.startfile(output_path)  # Windows系统专用
        else:
            messagebox.showerror("错误", "输出文件不存在")

if __name__ == "__main__":
    import tempfile
    root = tk.Tk()
    app = CodeToTXTApp(root)
    root.mainloop()